﻿using Microsoft.CSharp;
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Web.Services.Description;
using System.Xml;

namespace NursingFormSystem
{
    /// <summary>
    /// 动态Web服务代理方法
    /// </summary>
    public class InvokeWebservice : ICodeCompiler
    {
        private WebClient wc = new WebClient();
        private ServiceDescription sd = null;
        private ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
        private CodeNamespace cn = null;
        private CodeCompileUnit ccu = new CodeCompileUnit();
        private CompilerParameters cplist = new CompilerParameters();
        private CSharpCodeProvider csc = new CSharpCodeProvider();

        //private ICodeCompiler icc =null;
        private CompilerResults cr = null;

        private Assembly assembly = null;
        private Type t = null;
        private Stream stream = null;
        private Object obj = null;
        private MethodInfo mi = null;
        private static readonly ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim();
        private static IDictionary<string, object> _wsCache;

        private String _nameSpace;

        /// <summary>
        /// WebService对象集合。
        /// </summary>
        private static IDictionary<string, object> WsCache
        {
            get
            {
                if (_wsCache == null)
                {
                    try
                    {
                        readerWriterLockSlim.EnterWriteLock();
                        //双重校验
                        if (_wsCache == null)
                            _wsCache = new Dictionary<string, object>();
                    }
                    catch { }
                    finally
                    {
                        readerWriterLockSlim.ExitWriteLock();
                    }
                }

                return _wsCache;
            }
        }

        /// <summary>
        /// 创建Webservice的代理类。
        /// </summary>
        ///<param name="url">WebService所在的URL地址。</param>
        ///<param name="nameSpace">动态编译时所用的命名空间，可自定义命名空间。</param>
        ///<param name="className">动态编译时所用的类名，类名必须与接口的实际类名一致。可通过WSDL查看具体类名。</param>
        ///<param name="methodMame">调用的方法名：可以为空。</param>
        public InvokeWebservice(string url, string nameSpace, string className, string methodMame = "") => CreateWebservice(url, nameSpace, className, methodMame);

        /// <summary>
        /// 创建Webservice的代理类。
        /// </summary>
        ///<param name="url">WebService所在的URL地址。</param>
        ///<param name="className">动态编译时所用的类名，类名必须与接口的实际类名一致。可通过WSDL查看具体类名。</param>
        public InvokeWebservice(string url, string className) => CreateWebservice(url, "ServiceBase.WebService.DynamicWebLoad", className, "");

        /// <summary>
        /// 创建Webservice的代理类。
        /// <para>自动获取类名，类名为接口的名字，如果二者不一致会导致动态编译错误。</para>
        /// </summary>
        /// <param name="url">WebService所在的URL地址。</param>
        public InvokeWebservice(string url) => CreateWebservice(url, "ServiceBase.WebService.DynamicWebLoad", GetClassName(url), "");

        /// <summary>
        /// 获取WS中的类名。
        /// <para>优先从WSDL解析，如果解析不了，从URL截取接口名称。</para>
        /// </summary>
        /// <param name="url">WebService地址。</param>
        /// <returns></returns>
        private string GetClassName(string url)
        {
            // 1.从WSDL中解析。
            XmlDocument xd = new XmlDocument();
            if ((url.Substring(url.Length - 4, 4) != null) && ((url.Substring(url.Length - 4, 4).ToLower() != "wsdl")))
            {
                if (url.IndexOf(".asmx") > -1)
                {
                    url = url + "?WSDL";
                }
                else
                {
                    url = url + "WSDL";
                }
            }
            xd.Load(url);
            var nodeList = xd.GetElementsByTagName("wsdl:service");
            if (nodeList != null && nodeList.Count > 0)
                return nodeList[0].Attributes["name"].Value;

            // 2.直接从URL中截取，此方法往往不准确。
            //假如URL为"http://localhost/InvokeService/Service1.asmx"
            //最终的返回值为 Service1
            string[] parts = url.Split('/');
            string[] pps = parts[parts.Length - 1].Split('.');
            return pps[0];
        }

        /// <summary>
        /// 生成WebService的代理类
        /// </summary>
        /// <param name="url">服务地址</param>
        /// <param name="nameSpace">命名空间</param>
        /// <param name="className">类名，必须跟直接的类名一致，可以通过WSDL中找到实际类名。</param>
        /// <param name="methodName">方法名</param>
        private void CreateWebservice(string url, string nameSpace, string className, string methodName)
        {
            if (string.IsNullOrEmpty(url))
                throw new Exception("WebService的服务地址不能为空！");
            if ((url.Substring(url.Length - 4, 4) != null) && ((url.Substring(url.Length - 4, 4).ToLower() != "wsdl")))
            {
                if (url.IndexOf(".asmx") > -1)
                {
                    url = url + "?WSDL";
                }
                else
                {
                    url = url + "WSDL";
                }
            }
            if (!WsCache.ContainsKey(url))
            {
                // 并发控制
                readerWriterLockSlim.EnterWriteLock();
                // 动态编译
                stream = wc.OpenRead(url);
                sd = ServiceDescription.Read(stream);
                stream.Close();
                sdi.AddServiceDescription(sd, "", "");
                cn = new CodeNamespace(_nameSpace = nameSpace);
                ccu.Namespaces.Add(cn);
                sdi.Import(cn, ccu);
                cplist.GenerateExecutable = false;
                cplist.GenerateInMemory = true;
                cplist.ReferencedAssemblies.Add("System.dll");
                cplist.ReferencedAssemblies.Add("System.XML.dll");
                cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
                cplist.ReferencedAssemblies.Add("System.Data.dll");
                cr = CodeDomProvider.CreateProvider("CSharp").CompileAssemblyFromDom(cplist, ccu);

                if (true == cr.Errors.HasErrors)
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    foreach (CompilerError ce in cr.Errors)
                    {
                        sb.Append(ce.ToString());
                        sb.Append(System.Environment.NewLine);
                    }
                    throw new Exception(sb.ToString());
                }
                assembly = cr.CompiledAssembly;
                t = assembly.GetType(nameSpace + "." + className, true, true);
                obj = Activator.CreateInstance(t);
                WsCache.Add(url, obj); // 将动态编译的对象缓存到列表。
            }
            else
            {
                obj = WsCache[url];
                t = obj.GetType();
            }
            if (methodName.Length > 0)
                mi = t.GetMethod(methodName);
        }

        ///<summary>
        /// 获取WebService方法的数据。
        ///</summary>
        ///<param name="args">传入参数，本参数为顺序参数。</param>
        /// <returns></returns>
        public T GetWebserviceMethodData<T>(object[] args)
        {
            try
            {
                return (T)mi.Invoke(obj, args);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
            }
        }

        /// <summary>
        /// 获取指定方法名的调用结果数据。
        /// </summary>
        /// <param name="methodname">方法名称。</param>
        /// <param name="args">传入参数，本参数为顺序参数。</param>
        /// <returns></returns>
        public T GetWebserviceMethodData<T>(string methodname, object[] args)
        {
            try
            {
                if (methodname.Length > 0)
                    mi = t.GetMethod(methodname);
                return (T)mi.Invoke(obj, args);
            }
            catch (Exception ex)
            {
                Exception e = ex.InnerException ?? ex;
                throw new Exception(e.Message, new Exception(e.StackTrace));
            }
        }

        /// <summary>
        /// 根据指定的类名方法名称获取数据。
        /// </summary>
        /// <param name="className">类名</param>
        /// <param name="methodName">方法名</param>
        /// <param name="args">传入参数，本参数为顺序参数</param>
        /// <returns></returns>
        public T GetWebserviceMethodData<T>(string className, string methodName, object[] args)
        {
            try
            {
                t = assembly.GetType(_nameSpace + "." + className, true, true);
                obj = Activator.CreateInstance(t);
                if (methodName.Length > 0)
                    mi = t.GetMethod(methodName);
                return (T)mi.Invoke(obj, args);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
            }
        }

        #region ICodeCompiler 成员

        public CompilerResults CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit compilationUnit)
        {
            throw new NotImplementedException();
        }

        public CompilerResults CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] compilationUnits)
        {
            throw new NotImplementedException();
        }

        public CompilerResults CompileAssemblyFromFile(CompilerParameters options, string fileName)
        {
            throw new NotImplementedException();
        }

        public CompilerResults CompileAssemblyFromFileBatch(CompilerParameters options, string[] fileNames)
        {
            throw new NotImplementedException();
        }

        public CompilerResults CompileAssemblyFromSource(CompilerParameters options, string source)
        {
            throw new NotImplementedException();
        }

        public CompilerResults CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources)
        {
            throw new NotImplementedException();
        }

        #endregion ICodeCompiler 成员
    }
}